<template>
  <div class="dg-body">
    <div class="dg-body-fixed-area" ref="dgScrollAreaInstance" @scroll="onVerticalScroll">
      <div class="dg-bg">
        <div class="dg-bg-container">
          <div v-for="node in nodes"
               v-if="!node.hide"
               :key="node.id"
               class="dg-row"
               :class="node.clazz"
               :style="{height: node.height + 'px'}"
               @mouseover="onRowMouseOver(node)" @mouseout="onRowMouseOut(node)" @click="onRowClick(node)"></div>
        </div>
        <div v-if="contentHasScrollBar" :style="{height: scrollBarWidth + 'px'}"></div>
      </div>
      <div class="dg-rows-left">
        <div class="dg-body-left-column" v-for="(col, idx) in leftFixedLeafColumns" :key="col.field" v-if="!col.hide" :style="leftFixedLeafColumnStyles[idx]">
          <d-g-body-cell v-for="node in nodes"
                         v-if="!node.hide"
                         :key="node.id"
                         :style="{height: node.height + 'px'}"
                         :column="col"
                         :node="node"
                         :class="node.clazz"
                         :root-config="rootConfig"
                         @resize="handleCellResize(node, $event)"
                         @check="handleNodeCheck"
                         @toggle="handleToggle"
                         @mouseover="onRowMouseOver" @mouseout="onRowMouseOut" @click="onRowClick"></d-g-body-cell>
        </div>
      </div>
      <div class="dg-rows-right" :style="{left: leftFixedWidth + centerWidth + 'px', width: rightFixedWidth + 'px'}">
        <div class="dg-body-right-column" v-for="(col, idx) in rightFixedLeafColumns" :key="col.field" v-if="!col.hide" :style="rightFixedLeafColumnStyles[idx]">
          <d-g-body-cell v-for="node in nodes"
                         v-if="!node.hide"
                         :key="node.id"
                         :style="{height: node.height + 'px'}"
                         :column="col"
                         :node="node"
                         :class="node.clazz"
                         :root-config="rootConfig"
                         @resize="handleCellResize(node, $event)"
                         @check="handleNodeCheck"
                         @toggle="handleToggle"
                         @mouseover="onRowMouseOver" @mouseout="onRowMouseOut" @click="onRowClick"></d-g-body-cell>
        </div>
      </div>
    </div>

    <div class="dg-rows-normal" ref="contentInstance" :style="{left: leftFixedWidth + 'px', width: (contentHasScrollBar ? centerWidth : normalWidth) + 'px'}" @scroll="onHorizontalScroll" @mousewheel="onContentMouseWheel">
      <div style="overflow: hidden; height: 100%;" :style="{width: normalWidth + 'px'}">
        <div class="dg-rows-normal-content" :style="{height: totalRowsHeight + 'px', width: normalWidth + 'px', top: -currentVerticalScroll + 'px'}">
          <div class="dg-body-normal-column" v-for="(col, idx) in normalLeafColumns" :key="col.field" v-if="!col.hide" :style="normalLeafColumnStyles[idx]">
            <d-g-body-cell v-for="node in nodes"
                           v-if="!node.hide"
                           :key="node.id"
                           :style="{height: node.height + 'px'}"
                           :column="col"
                           :node="node"
                           :class="node.clazz"
                           :root-config="rootConfig"
                           @resize="handleCellResize(node, $event)"
                           @check="handleNodeCheck"
                           @toggle="handleToggle"
                           @mouseover="onRowMouseOver" @mouseout="onRowMouseOut" @click="onRowClick"></d-g-body-cell>
          </div>
        </div>
      </div>
    </div>

    <div class="dg-body-no-data" v-if="isNoData"><span>{{locale.noData}}</span></div>
    <div class="dg-body-loading" v-if="isLoading"><span>{{locale.loading}}</span></div>
  </div>
</template>

<script>
  import _ from 'lodash'
  import Vue from 'vue'
  import DGBodyCell from './DGBodyCell.vue'
  import utils from '../utils'

  let DEFAULT_ROW_HEIGHT = 28

  export default {
    components: {DGBodyCell},
    props: {
      rootConfig: {
        type: Object,
        default () {
          return {}
        }
      },
      isLoading: {},
      leftFixedLeafColumns: Array,
      rightFixedLeafColumns: Array,
      normalLeafColumns: Array,
      dataSource: Array,
      width: Number
    },
    data () {
      return {
        contentHasScrollBar: false,
        nodes: [],
        leftFixedLeafColumnStyles: [],
        rightFixedLeafColumnStyles: [],
        normalLeafColumnStyles: [],
        leftFixedWidth: 0,
        rightFixedWidth: 0,
        normalWidth: 0,
        centerWidth: 0,
        totalRowsHeight: 0,
        currentVerticalScroll: 0,
        scrollBarWidth: utils.scrollBarWidth
      }
    },
    computed: {
      locale () {
        return this.rootConfig.locale || {}
      },
      isNoData () {
        return !this.isLoading && (!this.dataSource || this.dataSource.length === 0)
      },
      childrenProp () {
        return this.rootConfig.childrenProp || 'children'
      }
    },
    methods: {
      refreshPosition () {
        let tmpLeftArr = [[], [], []]
        _.forEach([this.leftFixedLeafColumns, this.rightFixedLeafColumns, this.normalLeafColumns], (cols, idx) => {
          let left = 0
          _.forEach(cols, (col, idx01) => {
            tmpLeftArr[idx][idx01] = {left, width: col._width}
            left += col._width
          })
        })
        this.leftFixedWidth = _.sum(_.map(tmpLeftArr[0], e => { return e.width }))
        this.rightFixedWidth = _.sum(_.map(tmpLeftArr[1], e => { return e.width }))
        this.normalWidth = _.sum(_.map(tmpLeftArr[2], e => { return e.width }))
        let centerWidth = this.width - this.leftFixedWidth - this.rightFixedWidth
        this.centerWidth = centerWidth < 0 ? 0 : centerWidth
        this.leftFixedLeafColumnStyles = _.map(tmpLeftArr[0], e => { return {left: `${e.left}px`, width: `${e.width}px`} })
        this.rightFixedLeafColumnStyles = _.map(tmpLeftArr[1], e => { return {left: `${e.left}px`, width: `${e.width}px`} })
        this.normalLeafColumnStyles = _.map(tmpLeftArr[2], e => { return {left: `${e.left}px`, width: `${e.width}px`} })
        this.contentHasScrollBar = centerWidth < this.normalWidth
      },
      dealNodeData (node, parentNode, listIdx) {
        node.hasChildren = !!_.get(node.data, this.childrenProp)
        node.parent = parentNode
        node.level = parentNode ? parentNode.level + 1 : 0
        node.data.$index = parentNode ? parentNode.data.$index + '.' + (listIdx + 1) : `${listIdx + 1}`
        node.id = (this.rootConfig.rowKeyProp && _.get(node.data, this.rootConfig.rowKeyProp)) || node.data.$index
      },
      initDataSource (val) {
        let newNodes = []
        let totalRowsHeight = 0
        if (val && val.length > 0) {
          _.forEach(val, (item, idx) => {
            let rowHeight = this.rootConfig.rowHeight
            if (rowHeight && rowHeight instanceof Function) {
              rowHeight = rowHeight(item)
            }
            rowHeight = +rowHeight || DEFAULT_ROW_HEIGHT
            totalRowsHeight += rowHeight
            let node = {
              height: rowHeight,
              selected: false,
              checkDisabled: !!(this.rootConfig.rowCheckDisabled && this.rootConfig.rowCheckDisabled(item)),
              selectDisabled: !!(this.rootConfig.rowSelectionDisabled && this.rootConfig.rowSelectionDisabled(item)),
              data: item,
              level: 0
            }
            node.data.$index = `${idx + 1}`
            node.id = (this.rootConfig.rowKeyProp && _.get(item, this.rootConfig.rowKeyProp)) || node.data.$index
            if (this.rootConfig.isTree) {
              this.dealNodeData(node, null, idx)
            }
            newNodes.push(node)
          })
        }
        this.totalRowsHeight = totalRowsHeight
        this.nodes = newNodes
        if (this.rootConfig.isTree && this.rootConfig.allExpanded) {
          _.forEach(this.nodes, node => {
            if (node.hasChildren) {
              Vue.set(node, 'expanded', true)
              this.expandNode(node, _.indexOf(this.nodes, node))
            }
          })
        }
      },
      refreshRowClass (node) {
        let clazz = null
        if (node.selectDisabled) {
          clazz = 'dg-row-selection-disabled'
        } else if (node.isHover) {
          clazz = 'dg-row-hover'
        } else if (node.selected) {
          clazz = 'dg-row-selected'
        }
        Vue.set(node, 'clazz', clazz)
      },
      onVerticalScroll (evt) {
        this.currentVerticalScroll = evt.target.scrollTop
      },
      onHorizontalScroll (evt) {
        this.$emit('horizontal-scroll', evt.target.scrollLeft)
      },
      onContentMouseWheel (evt) {
        this.$refs.dgScrollAreaInstance.scrollTop += evt.deltaY
      },
      recalGridHeight () {
        let totalRowsHeight = 0
        _.forEach(this.nodes, node => {
          if (!node.hide) {
            totalRowsHeight += node.height
          }
        })
        this.totalRowsHeight = totalRowsHeight
        this.$emit('resize')
      },
      expandNode (node, idx) {
        if (node.children) {
          _.forEach(node.children, (child, subIdx) => {
            child.hide = false
            if (child.expanded) {
              this.expandNode(child, idx + subIdx + 1)
            }
          })
        } else {
          let children = []
          _.forEach(_.get(node.data, this.childrenProp), (item, subIdx) => {
            let rowHeight = this.rootConfig.rowHeight
            if (rowHeight && rowHeight instanceof Function) {
              rowHeight = rowHeight(item)
            }
            rowHeight = +rowHeight || DEFAULT_ROW_HEIGHT
            let newNode = {
              id: (this.rootConfig.rowKeyProp && _.get(item, this.rootConfig.rowKeyProp)) || idx + subIdx,
              height: rowHeight,
              selected: false,
              checkDisabled: !!(this.rootConfig.rowCheckDisabled && this.rootConfig.rowCheckDisabled(item)),
              selectDisabled: !!(this.rootConfig.rowSelectionDisabled && this.rootConfig.rowSelectionDisabled(item)),
              data: item
            }
            this.dealNodeData(newNode, node, subIdx)
            this.nodes.splice(idx + subIdx + 1, 0, newNode)
            children.push(newNode)
          })
          node.children = children
        }
      },
      collapseNode (node, idx) {
        for (let i = idx + 1; i < this.nodes.length; i++) {
          if (this.nodes[i].level <= node.level) {
            return
          } else {
            this.nodes[i].hide = true
          }
        }
      },
      handleToggle (node) {
        let idx = _.indexOf(this.nodes, node)
        Vue.set(node, 'expanded', !node.expanded)
        this[node.expanded ? 'expandNode' : 'collapseNode'](node, idx)
        this.recalGridHeight()
        this.nodes = [].concat(this.nodes)
      },
      handleNodeCheck (node) {
        let data = []
        let isAllChecked = true
        let ignoreDisabled = this.rootConfig.checkStateIgnoreDisabled !== false
        _.forEach(this.nodes, item => {
          // 如果此行被禁止勾选，则跳出
          if (this.rootConfig.rowCheckDisabled && this.rootConfig.rowCheckDisabled(item.data)) {
            // 如果不忽略禁止的，则直接标记为 未全选
            if (!ignoreDisabled) isAllChecked = false
            return
          }
          if (isAllChecked) {
            isAllChecked = item.checked
          }
          if (item.checked) {
            data.push(node.data)
          }
        })
        this.$emit('check-all', isAllChecked && data.length > 0)
        this.$emit('check-change', data)
      },
      handleCellResize (node) {
        let minHeight = this.rootConfig.rowHeight || DEFAULT_ROW_HEIGHT
        for (let c of node._cellCtxList || []) {
          if (c.height > minHeight) {
            minHeight = c.height
          }
        }
        this.$set(node, 'height', minHeight)
        this.recalGridHeight()
      },
      setCheckAllstate (isAllChecked) {
        let tmp = isAllChecked
        let hasValidNodes = false
        let ignoreDisabled = this.rootConfig.checkStateIgnoreDisabled !== false
        _.forEach(this.nodes, item => {
          // 如果此行被禁止勾选，则跳出
          if (this.rootConfig.rowCheckDisabled && this.rootConfig.rowCheckDisabled(item.data)) {
            // 如果不忽略禁止的，则直接标记为 未全选
            if (!ignoreDisabled) tmp = false
            return
          }
          !hasValidNodes && (hasValidNodes = true)
          Vue.set(item, 'checked', isAllChecked)
          if (!this.rootConfig.selectionNotEqualsChecking) {
            Vue.set(item, 'selected', isAllChecked)
            this.refreshRowClass(item)
          }
        })
        this.$emit('check-all', tmp && hasValidNodes)
      },
      onRowMouseOver (node) {
        Vue.set(node, 'isHover', true)
        this.refreshRowClass(node)
      },
      onRowMouseOut (node) {
        Vue.set(node, 'isHover', false)
        this.refreshRowClass(node)
      },
      onRowClick (node) {
        // 如果此行被禁止选择，则跳出
        if (this.rootConfig.rowSelectionDisabled && this.rootConfig.rowSelectionDisabled(node.data)) {
          return
        }
        let data = null
        Vue.set(node, 'selected', !node.selected)
        if (!this.rootConfig.selectionNotEqualsChecking) {
          Vue.set(node, 'checked', node.selected)
          this.handleNodeCheck(node)
        }
        this.refreshRowClass(node)
        if (!this.rootConfig.selectionMultiple) {
          _.forEach(this.nodes, item => {
            if (item !== node && item.selected) {
              Vue.set(item, 'selected', false)
              this.refreshRowClass(item)
            }
          })
          data = node.selected ? node.data : null
        } else {
          data = []
          _.forEach(this.nodes, item => {
            if (item.selected) {
              data.push(node.data)
            }
          })
        }
        this.$emit('selected-change', data)
      }
    },
    watch: {
      dataSource (val) {
        this.initDataSource(val)
      }
    },
    mounted () {
      this.initDataSource(this.dataSource)
    }
  }
</script>
